/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.catalina.tribes.transport.bio.util;

/**
 * The class <b>SingleRemoveSynchronizedAddLock</b> implement locking for
 * accessing the queue by a single remove thread and multiple add threads.
 * 
 * A thread is only allowed to be either the remove or an add thread.
 * 
 * The lock can either be owned by the remove thread or by a single add thread.
 * 
 * If the remove thread tries to get the lock, but the queue is empty, it will
 * block (poll) until an add threads adds an entry to the queue and releases the
 * lock.
 * 
 * If the remove thread and add threads compete for the lock and an add thread
 * releases the lock, then the remove thread will get the lock first.
 * 
 * The remove thread removes all entries in the queue at once and proceeses them
 * without further polling the queue.
 * 
 * The lock is not reentrant, in the sense, that all threads must release an
 * owned lock before competing for the lock again!
 * 
 * @author Rainer Jung
 * @author Peter Rossbach
 * @version 1.1
 */

public class SingleRemoveSynchronizedAddLock {

	public SingleRemoveSynchronizedAddLock() {
	}

	public SingleRemoveSynchronizedAddLock(boolean dataAvailable) {
		this.dataAvailable = dataAvailable;
	}

	/**
	 * Time in milliseconds after which threads waiting for an add lock are
	 * woken up. This is used as a safety measure in case thread notification
	 * via the unlock methods has a bug.
	 */
	private long addWaitTimeout = 10000L;

	/**
	 * Time in milliseconds after which threads waiting for a remove lock are
	 * woken up. This is used as a safety measure in case thread notification
	 * via the unlock methods has a bug.
	 */
	private long removeWaitTimeout = 30000L;

	/**
	 * The current remove thread. It is set to the remove thread polling for
	 * entries. It is reset to null when the remove thread releases the lock and
	 * proceeds processing the removed entries.
	 */
	private Thread remover = null;

	/**
	 * A flag indicating, if an add thread owns the lock.
	 */
	private boolean addLocked = false;

	/**
	 * A flag indicating, if the remove thread owns the lock.
	 */
	private boolean removeLocked = false;

	/**
	 * A flag indicating, if the remove thread is allowed to wait for the lock.
	 * The flag is set to false, when aborting.
	 */
	private boolean removeEnabled = true;

	/**
	 * A flag indicating, if the remover needs polling. It indicates, if the
	 * locked object has data available to be removed.
	 */
	private boolean dataAvailable = false;

	/**
	 * @return Value of addWaitTimeout
	 */
	public synchronized long getAddWaitTimeout() {
		return addWaitTimeout;
	}

	/**
	 * Set value of addWaitTimeout
	 */
	public synchronized void setAddWaitTimeout(long timeout) {
		addWaitTimeout = timeout;
	}

	/**
	 * @return Value of removeWaitTimeout
	 */
	public synchronized long getRemoveWaitTimeout() {
		return removeWaitTimeout;
	}

	/**
	 * Set value of removeWaitTimeout
	 */
	public synchronized void setRemoveWaitTimeout(long timeout) {
		removeWaitTimeout = timeout;
	}

	/**
	 * Check if the locked object has data available i.e. the remover can stop
	 * poling and get the lock.
	 * 
	 * @return True iff the lock Object has data available.
	 */
	public synchronized boolean isDataAvailable() {
		return dataAvailable;
	}

	/**
	 * Check if an add thread owns the lock.
	 * 
	 * @return True iff an add thread owns the lock.
	 */
	public synchronized boolean isAddLocked() {
		return addLocked;
	}

	/**
	 * Check if the remove thread owns the lock.
	 * 
	 * @return True iff the remove thread owns the lock.
	 */
	public synchronized boolean isRemoveLocked() {
		return removeLocked;
	}

	/**
	 * Check if the remove thread is polling.
	 * 
	 * @return True iff the remove thread is polling.
	 */
	public synchronized boolean isRemovePolling() {
		if (remover != null) {
			return true;
		}
		return false;
	}

	/**
	 * Acquires the lock by an add thread and sets the add flag. If any add
	 * thread or the remove thread already acquired the lock this add thread
	 * will block until the lock is released.
	 */
	public synchronized void lockAdd() {
		if (addLocked || removeLocked) {
			do {
				try {
					wait(addWaitTimeout);
				} catch (InterruptedException e) {
					Thread.currentThread().interrupted();
				}
			} while (addLocked || removeLocked);
		}
		addLocked = true;
	}

	/**
	 * Acquires the lock by the remove thread and sets the remove flag. If any
	 * add thread already acquired the lock or the queue is empty, the remove
	 * thread will block until the lock is released and the queue is not empty.
	 */
	public synchronized boolean lockRemove() {
		removeLocked = false;
		removeEnabled = true;
		if ((addLocked || !dataAvailable) && removeEnabled) {
			remover = Thread.currentThread();
			do {
				try {
					wait(removeWaitTimeout);
				} catch (InterruptedException e) {
					Thread.currentThread().interrupted();
				}
			} while ((addLocked || !dataAvailable) && removeEnabled);
			remover = null;
		}
		if (removeEnabled) {
			removeLocked = true;
		}
		return removeLocked;
	}

	/**
	 * Releases the lock by an add thread and reset the remove flag. If the
	 * reader thread is polling, notify it.
	 */
	public synchronized void unlockAdd(boolean dataAvailable) {
		addLocked = false;
		this.dataAvailable = dataAvailable;
		if ((remover != null) && (dataAvailable || !removeEnabled)) {
			remover.interrupt();
		} else {
			notifyAll();
		}
	}

	/**
	 * Releases the lock by the remove thread and reset the add flag. Notify all
	 * waiting add threads, that the lock has been released by the remove
	 * thread.
	 */
	public synchronized void unlockRemove() {
		removeLocked = false;
		dataAvailable = false;
		notifyAll();
	}

	/**
	 * Abort any polling remover thread
	 */
	public synchronized void abortRemove() {
		removeEnabled = false;
		if (remover != null) {
			remover.interrupt();
		}
	}

}
